msg_tool\scripts\ex_hibit/
rld.rs

1//! ExHibit Script File (.rld)
2use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use crate::utils::encoding::*;
6use crate::utils::struct_pack::*;
7use anyhow::Result;
8use msg_tool_macro::*;
9use serde::ser::SerializeStruct;
10use serde::{Deserialize, Serialize};
11use std::collections::BTreeMap;
12use std::io::{Read, Seek, Write};
13
14#[derive(Debug)]
15/// Builder for ExHibit RLD script files
16pub struct RldScriptBuilder {}
17
18impl RldScriptBuilder {
19    /// Creates a new instance of `RldScriptBuilder`
20    pub fn new() -> Self {
21        Self {}
22    }
23}
24
25impl ScriptBuilder for RldScriptBuilder {
26    fn default_encoding(&self) -> Encoding {
27        Encoding::Cp932
28    }
29
30    fn build_script(
31        &self,
32        buf: Vec<u8>,
33        filename: &str,
34        encoding: Encoding,
35        _archive_encoding: Encoding,
36        config: &ExtraConfig,
37        _archive: Option<&Box<dyn Script>>,
38    ) -> Result<Box<dyn Script>> {
39        Ok(Box::new(RldScript::new(buf, filename, encoding, config)?))
40    }
41
42    fn extensions(&self) -> &'static [&'static str] {
43        &["rld"]
44    }
45
46    fn script_type(&self) -> &'static ScriptType {
47        &ScriptType::ExHibit
48    }
49
50    fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
51        if buf_len >= 4 && buf.starts_with(b"\0DLR") {
52            return Some(10);
53        }
54        None
55    }
56}
57
58#[derive(Debug)]
59struct XorKey {
60    xor_key: u32,
61    keys: [u32; 0x100],
62}
63
64#[derive(Debug, StructPack, StructUnpack)]
65struct Header {
66    ver: u32,
67    offset: u32,
68    count: u32,
69}
70
71#[derive(Clone, Debug, StructPack, StructUnpack)]
72struct Op {
73    op: u16,
74    init_count: u8,
75    unk: u8,
76}
77
78impl PartialEq<u16> for Op {
79    fn eq(&self, other: &u16) -> bool {
80        self.op == *other
81    }
82}
83
84impl Op {
85    pub fn str_count(&self) -> u8 {
86        self.unk & 0xF
87    }
88}
89
90#[derive(Clone, Debug)]
91struct OpExt {
92    op: Op,
93    strs: Vec<String>,
94    ints: Vec<u32>,
95}
96
97impl<'de> Deserialize<'de> for OpExt {
98    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
99    where
100        D: serde::Deserializer<'de>,
101    {
102        #[derive(Deserialize)]
103        struct OpExtHelper {
104            op: u16,
105            unk: u8,
106            strs: Vec<String>,
107            ints: Vec<u32>,
108        }
109
110        let helper = OpExtHelper::deserialize(deserializer)?;
111        let init_count = helper.ints.len() as u8;
112        let str_count = helper.strs.len() as u8;
113        let unk = (helper.unk << 4) | (str_count & 0xF);
114
115        Ok(OpExt {
116            op: Op {
117                op: helper.op,
118                init_count,
119                unk,
120            },
121            strs: helper.strs,
122            ints: helper.ints,
123        })
124    }
125}
126
127impl Serialize for OpExt {
128    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
129        let mut state = serializer.serialize_struct("OpExt", 4)?;
130        state.serialize_field("op", &self.op.op)?;
131        state.serialize_field("unk", &((self.op.unk & 0xF0) >> 4))?;
132        state.serialize_field("strs", &self.strs)?;
133        state.serialize_field("ints", &self.ints)?;
134        state.end()
135    }
136}
137
138impl StructPack for OpExt {
139    fn pack<W: Write>(
140        &self,
141        writer: &mut W,
142        big: bool,
143        encoding: Encoding,
144        info: &Option<Box<dyn std::any::Any>>,
145    ) -> Result<()> {
146        self.op.op.pack(writer, big, encoding, info)?;
147        let init_count = self.ints.len() as u8;
148        init_count.pack(writer, big, encoding, info)?;
149        let unk = (self.op.unk & 0xF0) | (self.strs.len() as u8 & 0xF);
150        unk.pack(writer, big, encoding, info)?;
151        for i in &self.ints {
152            i.pack(writer, big, encoding, info)?;
153        }
154        for s in &self.strs {
155            let encoded = encode_string(encoding, s, true)?;
156            writer.write_all(&encoded)?;
157            writer.write_u8(0)?; // Null terminator for C-style strings
158        }
159        Ok(())
160    }
161}
162
163impl StructUnpack for OpExt {
164    fn unpack<R: Read + Seek>(
165        reader: &mut R,
166        big: bool,
167        encoding: Encoding,
168        info: &Option<Box<dyn std::any::Any>>,
169    ) -> Result<Self> {
170        let op = Op::unpack(reader, big, encoding, info)?;
171        let mut ints = Vec::with_capacity(op.init_count as usize);
172        for _ in 0..op.init_count {
173            let i = u32::unpack(reader, big, encoding, info)?;
174            ints.push(i);
175        }
176        let mut strs = Vec::with_capacity(op.str_count() as usize);
177        for _ in 0..op.str_count() {
178            let s = reader.read_cstring()?;
179            let s = decode_to_string(encoding, s.as_bytes(), true)?;
180            strs.push(s);
181        }
182        Ok(Self { op, strs, ints })
183    }
184}
185
186#[derive(Debug)]
187/// ExHibit RLD script file
188pub struct RldScript {
189    data: MemReader,
190    decrypted: bool,
191    xor_key: Option<XorKey>,
192    header: Header,
193    _flag: u32,
194    _tag: Option<String>,
195    ops: Vec<OpExt>,
196    is_def_chara: bool,
197    name_table: Option<BTreeMap<u32, String>>,
198    custom_yaml: bool,
199}
200
201impl RldScript {
202    /// Creates a new `RldScript`
203    ///
204    /// * `buf` - The buffer containing the RLD script data
205    /// * `filename` - The name of the file
206    /// * `encoding` - The encoding of the script
207    /// * `config` - Extra configuration options
208    pub fn new(
209        buf: Vec<u8>,
210        filename: &str,
211        encoding: Encoding,
212        config: &ExtraConfig,
213    ) -> Result<Self> {
214        let mut reader = MemReader::new(buf);
215        let mut magic = [0u8; 4];
216        reader.read_exact(&mut magic)?;
217        if &magic != b"\0DLR" {
218            return Err(anyhow::anyhow!("Invalid RLD script magic: {:?}", magic));
219        }
220        let is_def = std::path::Path::new(filename)
221            .file_stem()
222            .map(|s| s.to_ascii_lowercase() == "def")
223            .unwrap_or(false);
224        let is_def_chara = std::path::Path::new(filename)
225            .file_stem()
226            .map(|s| s.to_ascii_lowercase() == "defchara")
227            .unwrap_or(false);
228        let xor_key = if is_def {
229            if let Some(xor_key) = config.ex_hibit_rld_def_xor_key {
230                let keys = config
231                    .ex_hibit_rld_def_keys
232                    .as_deref()
233                    .cloned()
234                    .ok_or(anyhow::anyhow!("No keys provided for def RLD script"))?;
235                Some(XorKey {
236                    xor_key,
237                    keys: keys,
238                })
239            } else {
240                None
241            }
242        } else {
243            if let Some(xor_key) = config.ex_hibit_rld_xor_key {
244                let keys = config
245                    .ex_hibit_rld_keys
246                    .as_deref()
247                    .cloned()
248                    .ok_or(anyhow::anyhow!("No keys provided for RLD script"))?;
249                Some(XorKey {
250                    xor_key,
251                    keys: keys,
252                })
253            } else {
254                None
255            }
256        };
257        let header = Header::unpack(&mut reader, false, encoding, &None)?;
258        let mut decrypted = false;
259        if let Some(key) = &xor_key {
260            Self::xor(&mut reader.data, key);
261            decrypted = true;
262        }
263        let flag = reader.read_u32()?;
264        let tag = if flag == 1 {
265            let s = reader.read_cstring()?;
266            Some(decode_to_string(encoding, s.as_bytes(), true)?)
267        } else {
268            None
269        };
270        reader.pos = header.offset as usize;
271        let mut ops = Vec::with_capacity(header.count as usize);
272        for _ in 0..header.count {
273            let op = OpExt::unpack(&mut reader, false, encoding, &None)?;
274            ops.push(op);
275        }
276        let name_table = if is_def_chara {
277            None
278        } else {
279            match Self::try_load_name_table(filename, encoding, config) {
280                Ok(table) => Some(table),
281                Err(e) => {
282                    eprintln!("WARN: Failed to load name table: {}", e);
283                    crate::COUNTER.inc_warning();
284                    None
285                }
286            }
287        };
288        Ok(Self {
289            data: reader,
290            decrypted,
291            xor_key,
292            header,
293            _flag: flag,
294            _tag: tag,
295            ops,
296            is_def_chara,
297            name_table,
298            custom_yaml: config.custom_yaml,
299        })
300    }
301
302    fn try_load_name_table(
303        filename: &str,
304        encoding: Encoding,
305        config: &ExtraConfig,
306    ) -> Result<BTreeMap<u32, String>> {
307        let mut pb = std::path::Path::new(filename).to_path_buf();
308        pb.set_file_name("defChara.rld");
309        let f = crate::utils::files::read_file(&pb)?;
310        let f = Self::new(f, &pb.to_string_lossy(), encoding, config)?;
311        Ok(f.name_table()?)
312    }
313
314    fn xor(data: &mut Vec<u8>, key: &XorKey) {
315        let mut end = data.len().min(0xFFCF);
316        end -= end % 4;
317        let mut ri = 0;
318        for i in (0x10..end).step_by(4) {
319            let en_temp = u32::from_le_bytes([data[i], data[i + 1], data[i + 2], data[i + 3]]);
320            let temp_key = key.keys[ri & 0xFF] ^ key.xor_key;
321            let de_temp = (en_temp ^ temp_key).to_le_bytes();
322            data[i] = de_temp[0];
323            data[i + 1] = de_temp[1];
324            data[i + 2] = de_temp[2];
325            data[i + 3] = de_temp[3];
326            ri += 1;
327        }
328    }
329
330    fn name_table(&self) -> Result<BTreeMap<u32, String>> {
331        let mut names = BTreeMap::new();
332        for op in &self.ops {
333            if op.op == 48 {
334                if op.strs.is_empty() {
335                    return Err(anyhow::anyhow!("Op 48 has no strings"));
336                }
337                let name = op.strs[0].clone();
338                let data: Vec<_> = name.split(",").collect();
339                if data.len() < 4 {
340                    return Err(anyhow::anyhow!("Op 48 has invalid data: {}", name));
341                }
342                let id = data[0].parse::<u32>()?;
343                let name = data[3].to_string();
344                names.insert(id, name);
345            }
346        }
347        Ok(names)
348    }
349
350    fn write_script<W: Write + Seek>(
351        &self,
352        mut writer: W,
353        encoding: Encoding,
354        ops: &[OpExt],
355    ) -> Result<()> {
356        writer.write_all(&self.data.data[..self.header.offset as usize])?;
357        let op_count = ops.len() as u32;
358        if op_count != self.header.count {
359            writer.write_u32_at(12, op_count)?;
360        }
361        for op in ops {
362            op.pack(&mut writer, false, encoding, &None)?;
363        }
364        if self.data.data.len() > self.data.pos {
365            writer.write_all(&self.data.data[self.data.pos..])?;
366        }
367        Ok(())
368    }
369}
370
371impl Script for RldScript {
372    fn default_output_script_type(&self) -> OutputScriptType {
373        if self.is_def_chara {
374            return OutputScriptType::Custom;
375        }
376        OutputScriptType::Json
377    }
378
379    fn is_output_supported(&self, output: OutputScriptType) -> bool {
380        if self.is_def_chara {
381            return matches!(output, OutputScriptType::Custom);
382        }
383        true
384    }
385
386    fn default_format_type(&self) -> FormatOptions {
387        FormatOptions::None
388    }
389
390    fn custom_output_extension<'a>(&'a self) -> &'a str {
391        if self.custom_yaml { "yaml" } else { "json" }
392    }
393
394    fn extract_messages(&self) -> Result<Vec<Message>> {
395        let mut messages = Vec::new();
396        for op in &self.ops {
397            if op.op == 28 {
398                if op.strs.len() < 2 {
399                    return Err(anyhow::anyhow!("Op 28 has less than 2 strings"));
400                }
401                let name = if op.strs[0] == "*" {
402                    if op.ints.is_empty() {
403                        return Err(anyhow::anyhow!("Op 28 has no integers"));
404                    }
405                    let id = op.ints[0];
406                    self.name_table
407                        .as_ref()
408                        .and_then(|table| table.get(&id).cloned())
409                } else if op.strs[0] == "$noname$" {
410                    None
411                } else {
412                    Some(op.strs[0].clone())
413                };
414                let text = op.strs[1].clone();
415                messages.push(Message {
416                    name,
417                    message: text,
418                });
419            } else if op.op == 21 || op.op == 191 {
420                eprintln!("{op:?}");
421            }
422        }
423        Ok(messages)
424    }
425
426    fn import_messages<'a>(
427        &'a self,
428        messages: Vec<Message>,
429        mut file: Box<dyn WriteSeek + 'a>,
430        _filename: &str,
431        encoding: Encoding,
432        replacement: Option<&'a ReplacementTable>,
433    ) -> Result<()> {
434        let mut ops = self.ops.clone();
435        let mut mes = messages.iter();
436        let mut mess = mes.next();
437        for op in ops.iter_mut() {
438            if op.op == 28 {
439                let m = match mess {
440                    Some(m) => m,
441                    None => return Err(anyhow::anyhow!("Not enough messages.")),
442                };
443                if op.strs.len() < 2 {
444                    return Err(anyhow::anyhow!("Op 28 has less than 2 strings"));
445                }
446                if op.strs[0] != "*" && op.strs[0] != "$noname$" {
447                    let mut name = match &m.name {
448                        Some(name) => name.clone(),
449                        None => {
450                            return Err(anyhow::anyhow!("Message has no name"));
451                        }
452                    };
453                    if let Some(replacement) = replacement {
454                        for (k, v) in &replacement.map {
455                            name = name.replace(k, v);
456                        }
457                    }
458                    op.strs[0] = name;
459                }
460                let mut message = m.message.clone();
461                if let Some(replacement) = replacement {
462                    for (k, v) in &replacement.map {
463                        message = message.replace(k, v);
464                    }
465                }
466                op.strs[1] = message;
467                mess = mes.next();
468            }
469        }
470        if mess.is_some() || mes.next().is_some() {
471            return Err(anyhow::anyhow!("Too many messages provided."));
472        }
473        if self.decrypted {
474            let mut writer = MemWriter::new();
475            self.write_script(&mut writer, encoding, &ops)?;
476            if let Some(key) = &self.xor_key {
477                Self::xor(&mut writer.data, key);
478            }
479            file.write_all(&writer.data)?;
480        } else {
481            self.write_script(&mut file, encoding, &ops)?;
482        }
483        Ok(())
484    }
485
486    fn custom_export(&self, filename: &std::path::Path, encoding: Encoding) -> Result<()> {
487        let s = if self.is_def_chara {
488            let names = self.name_table()?;
489            if self.custom_yaml {
490                serde_yaml_ng::to_string(&names)
491                    .map_err(|e| anyhow::anyhow!("Failed to serialize to YAML: {}", e))?
492            } else {
493                serde_json::to_string_pretty(&names)
494                    .map_err(|e| anyhow::anyhow!("Failed to serialize to JSON: {}", e))?
495            }
496        } else {
497            if self.custom_yaml {
498                serde_yaml_ng::to_string(&self.ops)
499                    .map_err(|e| anyhow::anyhow!("Failed to serialize to YAML: {}", e))?
500            } else {
501                serde_json::to_string_pretty(&self.ops)
502                    .map_err(|e| anyhow::anyhow!("Failed to serialize to JSON: {}", e))?
503            }
504        };
505        let s = encode_string(encoding, &s, false)?;
506        let mut file = std::fs::File::create(filename)?;
507        file.write_all(&s)?;
508        Ok(())
509    }
510
511    fn custom_import<'a>(
512        &'a self,
513        custom_filename: &'a str,
514        mut file: Box<dyn WriteSeek + 'a>,
515        encoding: Encoding,
516        output_encoding: Encoding,
517    ) -> Result<()> {
518        let f = crate::utils::files::read_file(custom_filename)?;
519        let s = decode_to_string(output_encoding, &f, true)?;
520        let ops: Vec<OpExt> = if self.is_def_chara {
521            let mut ops = self.ops.clone();
522            let names: BTreeMap<u32, String> = if self.custom_yaml {
523                serde_yaml_ng::from_str(&s)
524                    .map_err(|e| anyhow::anyhow!("Failed to parse YAML: {}", e))?
525            } else {
526                serde_json::from_str(&s)
527                    .map_err(|e| anyhow::anyhow!("Failed to parse JSON: {}", e))?
528            };
529            for op in ops.iter_mut() {
530                if op.op == 48 {
531                    if op.strs.is_empty() {
532                        return Err(anyhow::anyhow!("Op 48 has no strings"));
533                    }
534                    let name = op.strs[0].clone();
535                    let data: Vec<_> = name.split(",").collect();
536                    if data.len() < 4 {
537                        return Err(anyhow::anyhow!("Op 48 has invalid data: {}", name));
538                    }
539                    let id = data[0].parse::<u32>()?;
540                    let name = names
541                        .get(&id)
542                        .cloned()
543                        .unwrap_or_else(|| data[3].to_string());
544                    let mut data = data.iter().map(|s| s.to_string()).collect::<Vec<_>>();
545                    data[3] = name;
546                    op.strs[0] = data.join(",");
547                }
548            }
549            ops
550        } else {
551            if self.custom_yaml {
552                serde_yaml_ng::from_str(&s)
553                    .map_err(|e| anyhow::anyhow!("Failed to parse YAML: {}", e))?
554            } else {
555                serde_json::from_str(&s)
556                    .map_err(|e| anyhow::anyhow!("Failed to parse JSON: {}", e))?
557            }
558        };
559        if self.decrypted {
560            let mut writer = MemWriter::new();
561            self.write_script(&mut writer, encoding, &ops)?;
562            if let Some(key) = &self.xor_key {
563                Self::xor(&mut writer.data, key);
564            }
565            file.write_all(&writer.data)?;
566        } else {
567            self.write_script(&mut file, encoding, &ops)?;
568        }
569        Ok(())
570    }
571}
572
573/// Load the keys from a file
574pub fn load_keys(path: Option<&String>) -> Result<Option<Box<[u32; 0x100]>>> {
575    if let Some(path) = path {
576        let f = crate::utils::files::read_file(path)?;
577        let mut reader = MemReader::new(f);
578        let mut keys = [0u32; 0x100];
579        for i in 0..0x100 {
580            keys[i] = reader.read_u32()?;
581        }
582        Ok(Some(Box::new(keys)))
583    } else {
584        Ok(None)
585    }
586}
587
588#[test]
589fn test_ser() {
590    let op = OpExt {
591        op: Op {
592            op: 28,
593            init_count: 1,
594            unk: 0x10 | 2,
595        },
596        strs: vec!["name".to_string(), "message".to_string()],
597        ints: vec![123],
598    };
599    let json = serde_json::to_string(&op).unwrap();
600    assert_eq!(
601        json,
602        r#"{"op":28,"unk":1,"strs":["name","message"],"ints":[123]}"#
603    );
604}
605
606#[test]
607fn test_de_ser() {
608    let json = r#"{"op":28,"unk":1,"strs":["name","message"],"ints":[123]}"#;
609    let op: OpExt = serde_json::from_str(json).unwrap();
610    assert_eq!(op.op.op, 28);
611    assert_eq!(op.op.init_count, 1);
612    assert_eq!(op.op.unk, 0x10 | 2);
613    assert_eq!(op.strs[0], "name");
614    assert_eq!(op.strs[1], "message");
615    assert_eq!(op.ints[0], 123);
616}